package au.id.wattle.chapman.j2as3;

import java.util.ArrayList;
import java.util.Arrays;

public class Tokenizer {
	private String stringToTokenize;
	private int pos;

	private String[] keywords = new String[] { "abstract", "boolean", "break",
			"byte", "case", "catch", "char", "class", "const", "continue",
			"default", "do", "double", "else", "extends", "false", "final",
			"finally", "float", "for", "goto", "if", "implements", "import",
			"instanceof", "int", "interface", "long", "native", "new", "null",
			"package", "private", "protected", "public", "return", "short",
			"static", "strictfp", "super", "switch", "synchronized", "this",
			"throw", "throws", "transient", "true", "try", "void", "volatile",
			"while" };

	private String[] symbols = new String[] { "(", ")", "{", "}", "<", ">",
			"<=", ">=", "[", "]", "*", "*=", "instanceof", ",", ";", ".", "~",
			"~=", "=", "==", "!", "!=", "^", "^=", "-", "--", "+", "++", "+=",
			"-=", ">>", "<<", "<<<", ">>=", "<<=", "<<<=", "/", "/=", "%",
			"%=", ":", "?", "&", "&=", "|", "&&", "||", "@" };

	public Tokenizer(String str) {
		this.stringToTokenize = str;
		pos = 0;
		Arrays.sort(symbols, new LengthComparator());
		Arrays.sort(keywords, new LengthComparator());
	}

	public Token nextToken() {
		if (pos >= stringToTokenize.length())
			return null;

		char c = currentChar();
		if (c == Character.MAX_VALUE)
			return null;
		
		int start = pos;
		String str;

		if (isWhitespace(c)) {
			skipWhitespace();
			c = currentChar();
			start = pos;
		}
		if (c == Character.MAX_VALUE)
			return null;

		if (isNumber(c)) {
			skipToStringEnd();
			str = stringToTokenize.substring(start, pos);
			return new Token(str, Token.NUMBER, pos);
		}

		if (c == '/') {
			if (nextChar() == '*') {
				skipUntil("*/");
				return new Token(stringToTokenize.substring(start, pos),
						Token.COMMENT, pos);
			} else if (nextChar() == '/') {
				skipUntil("\n");
				pos--;
				return new Token(stringToTokenize.substring(start, pos),
						Token.COMMENT, pos);
			}
		}

		if (isLetter(c) || c == '$' || c == '_') {
			skipToStringEnd();
			str = stringToTokenize.substring(start, pos);
			String type = Token.STRING_LITERAL;
			if (isKeyword(str))
				type = Token.KEYWORD;
			return new Token(str, type, pos);
		} else if (isSymbol(pos) != null) {
			str = isSymbol(pos);
			pos += str.length();
			return new Token(str, Token.SYMBOL, pos);
		} else if (c == '\"') { // a string
			skipUntilWithEsc('\"');
			return new Token(stringToTokenize.substring(start, pos),
					Token.STRING, pos);
		} else if (c == '\'') { // a char
			skipUntilWithEsc('\'');
			return new Token(stringToTokenize.substring(start, pos),
					Token.CHAR, pos);
		}
		return null;
	}

	private char currentChar() {
		if(pos >= stringToTokenize.length())
			return Character.MAX_VALUE;
		
		return stringToTokenize.charAt(pos);
	}

	private char nextChar() {
		if (pos >= stringToTokenize.length() - 1)
			return Character.MAX_VALUE;
		return stringToTokenize.charAt(pos + 1);
	}

	private void skipUntil(String exit) {
		pos++;
		int p = stringToTokenize.indexOf(exit, pos);
		if (p != -1)
			pos = p + exit.length();
	}

	/** Patch from http://www.physicsdev.com/blog/?p=14#comments - thanks */
	private void skipUntilWithEsc(char exit) {
		pos++;
		char c;
		while ((c = currentChar()) != exit && c != Character.MAX_VALUE) {
			if (c == '\\')
				pos++;
			pos++;
		}
		if(c == Character.MAX_VALUE)
			pos = stringToTokenize.length();
		else
			pos++;
	}

	private void skipWhitespace() {
		char c;
		c = currentChar();
		while (isWhitespace(c)) {
			pos++;
			c = currentChar();
		}
	}

	private Boolean isWhitespace(char str) {
		return str == ' ' || str == '\n' || str == '\t' || str == '\r';
	}

	private void skipToStringEnd() {
		char c;
		while (true) {
			c = currentChar();
			if (c == '.' && nextChar() == '.') {
				break;
			}
			if (isLetter(c) || isNumber(c) || c == '.' || c == '$' || c == '_') {
			} else {
				break;
			}
			pos++;
			c = currentChar();
		}
	}

	private Boolean isNumber(char str) {
		return (str >= 48 && str <= 57);
	}

	private Boolean isLetter(char str) {
		if (str >= 65 && str <= 90)
			return true;
		if (str >= 97 && str <= 122)
			return true;
		return false;
	}

	private Boolean isKeyword(String str) {
		return inArray(keywords, str);
	}

	private String isSymbol(int pos) {
		String s;
		for (int i = 0; i < symbols.length; i++) {
			s = symbols[i];
			if (pos + s.length() > stringToTokenize.length())
				continue;

			if (stringToTokenize.substring(pos, pos + s.length()).equals(s))
				return s;
		}
		return null;
	}

	private Boolean inArray(String[] arr, Object item) {
		for (int i = 0; i < arr.length; i++)
			if (arr[i].equals(item))
				return true;
		return false;
	}

	public ArrayList<Token> getAllTokens() {
		ArrayList<Token> arr = new ArrayList<Token>();
		pos = 0;
		Token t;
		while ((t = nextToken()) != null)
			arr.add(t);
		return arr;
	}
}
